/**
 * @(#)AdministradorHilos.java 3/04/2009
 *
 * Copyright (c) 2005-2010 PsiqueWare S.A. de C.V. All Rights Reserved.
 * This software is the proprietary information PsiqueWare S.A. de C.V.
 * Use is subject to license terms.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * <br/>Usuario : AngelElectrico
 * <br/>Creaci&oacute;n: 3/04/2009 - 08:55:02 AM
 * <br/>
 * <br/>
 * <br/><t5>Cambios :</t5>
 * <br/><b>Fecha | Autor  |  Cambios</b>
 * <ul>
 * <li>3/04/2009 | <a href = "mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a> | Creaci&oacute;n del Objeto.</li>
 *
 * </ul>
 */

package com.psiqueware.alheli.basico.hilos;

import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Admministrador en cargado de controlar los hilos de ejecuci&oacute;n.
 * <br/>Paquete Original: com.psiqueware.alheli.basico.threads
 * <br/>Nombre  Original: AdministradorHilos
 * <br/>Oprima para ver : <a href="AdministradorHilos.java.html"><i>C&oacute;digo Fuente</i> </a>
 *
 * @author <a href="mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a>
 * @version Revision: 1.1 3/04/2009 08:55:02 AM
 */
public final class AdministradorHilos
{

    /**
     * Pool de {@link Thread}s.
     */
    private final static ExecutorService m_executorService = Executors.newCachedThreadPool();

    /**
     * Listado de todos los {@link Hilo}s.
     */
    private static final List<Hilo> hilos = new ArrayList<Hilo>();

    /**
     * Listado de {@link ProcesoPeriodico}s.
     */
    private static final List<ProcesoPeriodico> periodicos = new ArrayList<ProcesoPeriodico>();


    /**
     * Le dice al hilo peri&oacute;dico que se debe apagar.
     */
    private static final AtomicBoolean apagar = new AtomicBoolean(false);

    /**
     * Thread de procesamiento peri&oacute;dico.
     */
    private static Thread procesadorPeriodico = new Thread(new Runnable()
    {

        public void run()
        {
            while (!apagar.get())
            {
                //Quitamos los threads muertos
                synchronized (hilos)
                {
                    final List<Hilo> hilosMuertos = new ArrayList<Hilo>();
                    for (final Hilo hilo : hilos)
                    {
                        if (!hilo.isVivo())
                        {
                            hilosMuertos.add(hilo);
                        }
                    }
                    for (final Hilo hilo : hilosMuertos)
                    {
                        hilos.remove(hilo);
                    }
                }

                //Lanzamos los procesos peri&oacute;dicos.
                synchronized (periodicos)
                {
                    for (final ProcesoPeriodico procesoPeriodico : periodicos)
                    {
                        final long ahora = new Date().getTime();
                        if (ahora >= procesoPeriodico.m_ultimaEjecucion + procesoPeriodico.m_millis.get() && !procesoPeriodico.m_hilo.isVivo())
                        {
                            procesoPeriodico.m_ultimaEjecucion = ahora;
                            final Hilo hiloProceso = procesoPeriodico.m_hilo;
                            ejecuta(hiloProceso);
                        }
                    }
                }
                try
                {
                    Thread.sleep(1);
                }
                catch (InterruptedException e)
                {
                    final String error = "<error><msg>El thread del procesador peri&oacute;dico fue interrumpido en su sue&ntilde;o.</msg></error>";
                    throw new RuntimeException(error, e);
                }
            }
        }
    });


    /**
     * Lanza el runnable en modalidad deamon.
     *
     * @param runnable a ejecutar.
     * @param nombre   del thread.
     *
     * @return el Hilo creado.
     */
    public static Hilo lanzarDemonio(final String nombre, final Runnable runnable)
    {
        if (apagar.get()) throw new AdministradorApagadoException();
        final Hilo hilo = new Hilo(runnable, nombre, true);
        lanzarHilo(hilo);
        return hilo;
    }

    /**
     * Lanza el runnable en modalidad deamon.
     *
     * @param runnable a ejecutar.
     * @param nombre   del thread.
     *
     * @return el Hilo creado.
     */
    public static Hilo lanzarSwing(final String nombre, final Runnable runnable)
    {
        if (apagar.get()) throw new AdministradorApagadoException();
        final Hilo hilo = new Hilo(runnable, nombre, true);
        hilo.setSwing(true);
        lanzarHilo(hilo);
        return hilo;
    }

    /**
     * Lanza el {@link Runnable} en primer plano.
     *
     * @param runnable a ejecutar.
     * @param nombre   del thread.
     *
     * @return el Hilo creado.
     */
    public static Hilo lanzarProceso(final String nombre, final Runnable runnable)
    {
        if (apagar.get()) throw new AdministradorApagadoException();

        final Hilo hilo = new Hilo(runnable, nombre);
        lanzarHilo(hilo);
        return hilo;
    }

    /**
     * Lanza el hilo dado.
     *
     * @param hilo a lanzar.
     */
    public static void lanzarHilo(final Hilo hilo)
    {


        if (hilo.isSwing())
        {
            EventQueue.invokeLater(new Runnable()
            {

                //Garantizamos que los threads de Swing no afecten el Hilo de Swing.
                public void run()
                {
                    try
                    {
                        hilo.getRunnable().run();
                    }
                    catch (final Throwable e)
                    {
                        System.err.println("El Hilo de Swing '" + hilo.getNombre() + "' solto una excepcion.");
                        e.printStackTrace();
                    }
                }
            });
        }
        else
        {
            //m_executorService.execute(hilo.getRunnable());
            final Thread t = new Thread(hilo.getRunnable());
            hilo.setThread(t);
            synchronized (hilos)
            {
                hilos.add(hilo);
            }

            t.setName(hilo.getNombre());
            t.setDaemon(hilo.isSegundoPlano());
            t.setPriority(hilo.getPrioridad());
            t.start();
        }

    }


    /**
     * Dice si un hilo ya se encuentra en el Sistema.
     *
     * @param nombreHilo del hilo buscado.
     *
     * @return <code>true</code> si ya esta corriendo; <code>false</code> en caso contrario.
     */
    public static boolean contains(final String nombreHilo)
    {
        Hilo salida = buscaHilo(nombreHilo);
        return null != salida;
    }

    /**
     * Busca un {@link Hilo} a partir de su nombre.
     *
     * @param nombreHilo buscado.
     *
     * @return el {@link Hilo} si lo encuentra; <code>null</code> en otro caso.
     */
    public static Hilo buscaHilo(final String nombreHilo)
    {
        Hilo salida = null;
        synchronized (hilos)
        {
            for (final Hilo hilo : hilos)
            {
                if (hilo.getNombre().equals(nombreHilo))
                {
                    salida = hilo;
                    break;
                }
            }
        }
        return salida;
    }

    /**
     * Lanza el {@link Runnable} peri&oacute;diocamente cada "millis" milisegundos.
     *
     * @param hilo   a ejecutar.
     * @param millis tiempo que debe pasar antes de que se vuelva a lanzar el proceso.
     */
    public static void lanzarProcesoPeriodico(final Hilo hilo, final AtomicLong millis)
    {
        if (apagar.get()) throw new AdministradorApagadoException();

        final ProcesoPeriodico periodico = new ProcesoPeriodico();
        periodico.m_millis = millis;
        periodico.m_hilo = hilo;
        synchronized (periodicos)
        {
            periodicos.add(periodico);
        }

        //No lo iniciamos hasta que sea necesario.
        if (!procesadorPeriodico.isAlive()) procesadorPeriodico.start();
    }

    /**
     * Detiene un proceso peri&oacute;dico elimin&aacute;ndolo del administrador.
     * <p/>
     * <strong>Nota</strong>: Este m&eacute;todo no mata al hilo en caso de estar vivo.
     *
     * @param hilo a detener.
     *
     * @return <code>true</code> si se detuvo la periodicidad del proceso; <code>false</code> en caso contrario.
     */
    public static boolean detenerProcesoPeriodico(final Hilo hilo)
    {
        boolean salida = false;
        synchronized (periodicos)
        {
            ProcesoPeriodico remover = null;
            for (final ProcesoPeriodico periodico : periodicos)
            {
                if (periodico.m_hilo == hilo)
                {
                    remover = periodico;
                    salida = true;
                    break;
                }
            }
            if (salida) periodicos.remove(remover);
        }
        return salida;
    }


    /**
     * Apaga todos los hilos.
     * <p/>
     * Este es un m&eacute;todo s&iacute;ncrono.
     *
     * @param millis tiempo m&aacute;ximo de gracia para que los hilos terminen sus labores.
     *
     * @return <code>true</code> si se tuvo que forzar a que se apagar&aacute;n los threads; <code>false</code> en caso contrario.
     */
    public static synchronized boolean apagar(final long millis)
    {
        boolean salida = false;
        apagar.set(true);
        final long tiempoVencido = new Date().getTime() + millis;

        while (new Date().getTime() < tiempoVencido)
        {
            boolean algunoVivo = false;
            synchronized (hilos)
            {
                for (final Hilo hilo : hilos)
                {
                    algunoVivo = algunoVivo || hilo.isVivo();
                }
            }
            synchronized (periodicos)
            {
                for (final ProcesoPeriodico periodico : periodicos)
                {
                    algunoVivo = algunoVivo || periodico.m_hilo.isVivo();
                }
            }
            if (!algunoVivo) break;
        }
        synchronized (hilos)
        {
            for (final Hilo hilo : hilos)
            {
                salida = salida || apagaHilo(hilo);
            }
        }
        synchronized (periodicos)
        {
            for (final ProcesoPeriodico periodico : periodicos)
            {
                salida = salida || apagaHilo(periodico.m_hilo);
            }
        }
        return salida;
    }

    /**
     * Detiene un thread sin importar las consecuencias.
     *
     * @param hilo a detener.
     *
     * @return true si se tuvo que forzar al thread a detenerse; false en caso contrario.
     *
     * @noinspection deprecation
     */
    public static boolean apagaHilo(final Hilo hilo)
    {
        boolean salida = false;
        if (hilo.isVivo())
        {
            final Thread thread = hilo.getThread();
            try
            {
                salida = true;
                thread.stop();
            }
            catch (final Throwable t)
            {

            }
        }
        return salida;
    }

    /**
     * Ejecuta un hilo.
     *
     * @param hilo a ejecutar.
     */
    private static void ejecuta(final Hilo hilo)
    {
        try
        {
            if (!hilo.isVivo())
            {
                final Thread thread = new Thread(hilo.getRunnable());
                hilo.setThread(thread);
                thread.start();
            }
        }
        catch (final Throwable t)
        {

        }
    }

    /**
     * Hilo que se ejecuta peri&oacute;dicamente.
     * <br/>Paquete Original: com.psiqueware.alheli.basico.hilos
     * <br/>Nombre  Original: AdministradorHilos
     * <br/><a href="AdministradorHilos.java.html"><i>Ver C&oacute;digo Fuente</i> </a>
     *
     * @author <a href="mailto:AngelElectrico@PsiqueWare.com">Carlos Alberto Hern&aacute;ndez Morales</a>
     * @version Revision: 1.0
     */
    public static class ProcesoPeriodico
    {

        /**
         * Hilo a procesar.
         */
        Hilo m_hilo;

        /**
         * Tiempo entre ejecuciones.
         */
        AtomicLong m_millis;

        /**
         * Fecha de la ultima ejecucion en milisegundos.
         */
        long m_ultimaEjecucion;

        public String toString()
        {
            return m_hilo.toString();
        }
    }

    /**
     * Devuelve el Listado de todos los {@link Hilo}s.
     *
     * @return valor de 'hilos'.
     *
     * @see List<Hilo>
     */
    public static List<Hilo> getHilos()
    {
        return new ArrayList<Hilo>(hilos);
    }

    /**
     * Devuelve el Listado de {@link ProcesoPeriodico}s.
     *
     * @return valor de 'periodicos'.
     *
     * @see List<ProcesoPeriodico>
     */
    public static List<ProcesoPeriodico> getPeriodicos()
    {
        return new ArrayList<ProcesoPeriodico>(periodicos);
    }
}

